/*
 * Copyright 2007-2008 Andrew O'Malley
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package ws.quokka.plugin.lifecycle;

import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.ResourceCollection;
import org.apache.tools.ant.Project;

import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.Collections;


/**
 * LifeCycleState contains the state that is accessible from plugins.
 * It is usually accessed via {@link ws.quokka.plugin.lifecycle.LifeCycleHelper#getState()}, which
 * in turns acesses it via {@link ws.quokka.core.plugin_spi.Resources#getPluginState()}
 * with a key of LifeCycleState.class.getName().
 * <br>
 * LifeCycleState is thread-safe. As such, all mutable objects returned are copies. To
 * modify the state use the modification methods.
 */
public class LifeCycleState {
    //~ Instance fields ------------------------------------------------------------------------------------------------

    private static final String FILTERED_RESOURCES = "filteredResources";
    private static final String UNFILTERED_RESOURCES = "unfilteredResources";
    private static final String FILTERED_TEST_RESOURCES = "filteredTestResources";
    private static final String UNFILTERED_TEST_RESOURCES = "unfilteredTestResources";
    private static final String INSTRUMENTED_TEST_PATH = "InstrumentedTestPath";
    private static final String GENERATED_SOURCES = "generatedSources";
    private static final String GENERATED_TEST_SOURCES = "generatedTestSources";

    private Project project;
    private Map state;

    //~ Constructors ---------------------------------------------------------------------------------------------------

    public LifeCycleState(Map state, Project project) {
        this.state = state;
        this.project = project;
    }

    //~ Methods --------------------------------------------------------------------------------------------------------

    private List getList(String key) {
        List list = (List) state.get(key);
        if (list == null) {
            list = new ArrayList();
            state.put(key, list);
        }
        return list;
    }

    /**
     * Returns a copy so that iterating is thread safe. Also wraps as immutable as a reminder to
     * use the add methods to modify.
     */
    private List getListCopy(String key) {
        synchronized (state) {
            return Collections.unmodifiableList(new ArrayList(getList(key)));
        }
    }

    private void addResource(String key, ResourceCollection rc) {
        synchronized (state) {
            List resources = getList(key);
            resources.add(rc);
        }
    }

    private Path getPath(String key) {
        Path path = (Path) state.get(key);
        if (path == null) {
            path = new Path(project);
            state.put(key, path);
        }
        return path;
    }

    private void addPath(String key, Path newPath) {
        synchronized (state) {
            Path path = getPath(key);
            path.add(newPath);
        }
    }

    /**
     * Returns a copy of the path to prevent unsafe modification
     */
    private Path getPathCopy(String key) {
        synchronized (state) {
            return (Path) getPath(key).clone();
        }
    }

    /**
     * Returns a read-only list of filtered resources
     */
    public List getFilteredResources() {
        return getListCopy(FILTERED_RESOURCES);
    }

    /**
     * Adds a filtered resource
     */
    public void addFilteredResource(ResourceCollection rc) {
        addResource(FILTERED_RESOURCES, rc);
    }

    /**
     * Returns a read-only list of unfiltered resources
     */
    public List getUnfilteredResources() {
        return getListCopy(UNFILTERED_RESOURCES);
    }

    /**
     * Adds an unfiltered resource
     */
    public void addUnfilteredResource(ResourceCollection rc) {
        addResource(UNFILTERED_RESOURCES, rc);
    }

    /**
     * Returns a read-only list of filtered test resources
     */
    public List getFilteredTestResources() {
        return getListCopy(FILTERED_TEST_RESOURCES);
    }

    /**
     * Adds a filtered test resource
     */
    public void addFilteredTestResource(ResourceCollection rc) {
        addResource(FILTERED_TEST_RESOURCES, rc);
    }

    /**
     * Returns a read-only list of unfiltered test resources
     */
    public List getUnfilteredTestResources() {
        return getListCopy(UNFILTERED_TEST_RESOURCES);
    }

    /**
     * Adds an unfiltered test resource
     */
    public void addUnfilteredTestResource(ResourceCollection rc) {
        addResource(UNFILTERED_TEST_RESOURCES, rc);
    }

    /**
     * Returns a read-only copy of any generated sources
     */
    public Path getGeneratedSourcesPath() {
        return getPathCopy(GENERATED_SOURCES);
    }

    /**
     * Adds a generated sources path
     */
    public void addGeneratedSources(Path sources) {
        addPath(GENERATED_SOURCES, sources);
    }

    /**
     * Returns a read-only copy of any generated test sources
     */
    public Path getGeneratedTestSourcesPath() {
        return getPathCopy(GENERATED_TEST_SOURCES);
    }

    /**
     * Adds a generated test sources path
     */
    public void addGeneratedTestSources(Path sources) {
        addPath(GENERATED_TEST_SOURCES, sources);
    }

    /**
     * Returns a read-only copy of the instrumented test path
     */
    public Path getInstrumentedTestPath() {
        return getPathCopy(INSTRUMENTED_TEST_PATH);
    }

    /**
     * Adds an instrumented test path
     */
    public void addInstrumentedTestPath(Path path) {
        addPath(INSTRUMENTED_TEST_PATH, path);
    }
}
